package com.atguigu.gmall.common.config;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.entity.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Configuration
@Slf4j
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback,
        RabbitTemplate.ReturnCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    //不可用构造器 使用后置实例化
    @PostConstruct
    public void MQProducerAckConfig() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    //交换机应答 成功失败都应答
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("交换机成功接收到了消息!消息是:" + JSONObject.toJSONString(correlationData));
        } else {
            log.error("交换机接收消息失败!!!原因是:" + cause);
            //指定重新发送消息次数  如果还不成功 打日志人工处理
            //四个要素 : 交换机 routingKey 消息 重发次数
            reTrySendMessage(correlationData);
        }
    }

    private void reTrySendMessage(CorrelationData correlationData) {
        //实际并没有强转
        GmallCorrelationData gmallCorrelationData = (GmallCorrelationData) correlationData;
        int retryCount = gmallCorrelationData.getRetryCount();
        if (retryCount < 3) {
            retryCount++;
            //设置重发次数
            //另一种方式
            //message.getMessageProperties().getHeaders().put("retryCount",3);
            gmallCorrelationData.setRetryCount(retryCount);

            //更新Redis缓存中的数据 为了队列应答失败
            redisTemplate.opsForValue().set(gmallCorrelationData.getId(), JSONObject.toJSONString(gmallCorrelationData));

            rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),
                    gmallCorrelationData.getRoutingKey(), gmallCorrelationData.getMessage(),
                    gmallCorrelationData);//为避免失败还需带上gmallCorrelationData
        } else {//发送失败次数过多   {} 占位符
//            log.error("发送次数已耗尽:{}:{}", JSONObject.toJSONString(gmallCorrelationData),"he_ze_cao_xian");
            log.error("发送次数已耗尽:{}", JSONObject.toJSONString(gmallCorrelationData));
        }

    }

    //队列应答 只有失败应答->routingKey错误
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.error("队列应答失败!");
        log.error("replyCode是" + replyCode);
        log.error("replyText是" + replyText);
        log.error("exchange是" + exchange);
        log.error("routingKey" + routingKey);
        //指定重新发送消息次数  如果还不成功 打日志人工处理
        //如何知道发送的次数?借助redis
        String uuid = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
        String json = (String) redisTemplate.opsForValue().get(uuid);
        GmallCorrelationData gmallCorrelationData = JSONObject.parseObject(json, GmallCorrelationData.class);
        //判断是否是延迟消息,由于基于死信队列的和基于插件的共用一套,所以这里无法确定到底是哪种,只能判断为延迟消息
        //TODO 基于死信队列和基于延迟插件的各用一套 偏爱插件 因为插件简单
        //因为基于插件的延迟消息的小bug  -->这样基于死信队列的延迟消息失败也不会重发了
        if (!gmallCorrelationData.isDelay()) {
            reTrySendMessage(gmallCorrelationData);
        } else {
            log.info("是延迟消息触发的队列应答失败!可以选择不重新发送:交换机:{},RoutingKey:{}", exchange, routingKey);
            //万一真的失败了呢?而不是延迟消息在交换机上造成队列应答失败
            //小bug能接受吗?不能就用死信队列
        }
    }
}
